package io.gitee.zhucan123.core.infrastructure.util;

import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.ObjectWriter;

import javax.persistence.Query;
import javax.persistence.criteria.Predicate;
import java.util.*;

/**
 * 用于构建一组 mysql 语句，构建完毕后获得 where 片段列表，使用 or 或 and 连接这些片段，
 * 类的内部负责分配查询参数名
 * 非线程安全
 *
 * @author guodu
 * @date 2019/11/22
 */
public class MySQLBuilder {

  int counter = 0;
  List<String> where = new ArrayList<>();
  // List<Consumer<Query>> setQueries = new ArrayList<>();
  /**
   * where语句参数化查询，值 - 参数。而不是参数－值的map，一个值只需要一个参数，当一个where条件有新的参数时，不需要分配新的参数名
   */
  Map<Object, String> valueNameMap;
  ObjectMapper objectMapper;
  ObjectWriter writer;
  MySQLBuilder parentBuilder;
  Predicate.BooleanOperator operator;
  String joinOperator;

  public MySQLBuilder(Predicate.BooleanOperator operator, ObjectMapper objectMapper) {
    this.objectMapper = objectMapper;
    this.writer = objectMapper.writer();
    this.valueNameMap = new HashMap<>();
    this.operator = operator;
    this.joinOperator = " " + operator.name() + " ";
  }

  private MySQLBuilder beginSubBuilder(Predicate.BooleanOperator operator) {
    MySQLBuilder builder = new MySQLBuilder(operator, this.objectMapper);
    builder.counter = this.counter;
    builder.parentBuilder = this;
    builder.valueNameMap = this.valueNameMap;
    return builder;
  }

  /**
   * 开启一个子查询.
   *
   * @return 子查询结束后需要调用 build
   */
  public MySQLBuilder and() {
    return beginSubBuilder(Predicate.BooleanOperator.AND);
  }

  public MySQLBuilder or() {
    return beginSubBuilder(Predicate.BooleanOperator.OR);
  }

  /**
   * 构建sql，如果是子查询，则将构建好的sql放到父查询中.
   * @return 构建的sql片段
   */
  public String build() {
    if (parentBuilder != null) {
      parentBuilder.counter = this.counter;
    }
    if (this.where.size() < 1) {
      return null;
    }
    String sql = String.join(this.joinOperator, this.where);
    if (parentBuilder != null) {
      parentBuilder.where.add("(" + sql + ")");
    }
    return sql;
  }

  /**
   * 给一个参数创建一个参数化查询名
   */
  String getParameterizedName(String paramName, Object paramValue) {
    if (this.valueNameMap.containsKey(paramValue)) {
      return this.valueNameMap.get(paramValue);
    } else {
      String parameterized = paramName + counter++;
      this.valueNameMap.put(paramValue, parameterized);
      return parameterized;
    }
  }

  /**
   * where JSON_CONTAINS(field, value, jsonPath).
   *
   * @param value 不能是数组，如果是数组需要调用whereJsonPathContainsAny或whereJsonPathContainsAll
   */
  public void whereJsonContains(String field, Object value, String jsonPath) throws JsonProcessingException {
    StringBuilder sql = new StringBuilder();
    final String valueJson = writer.writeValueAsString(value);
    String paramPath = getParameterizedName("path", jsonPath);
    String paramValue = getParameterizedName("value", valueJson);
    if (jsonPath.contains("*")) {
      // json_contains(json_extract(field, 'jsonPath'), valueName)
      sql.append("JSON_CONTAINS(JSON_EXTRACT(`");
      sql.append(field).append("`, :").append(paramPath).append("),:").append(paramValue).append(")");
    } else {
      // json_contains(field, valueName, 'jsonPath')
      sql.append("JSON_CONTAINS(`");
      sql.append(field).append("`, :").append(paramValue).append(",:").append(paramPath).append(")");
    }
    this.where.add(sql.toString());
  }

  /**
   * Where JSON_CONTAINS('[value]', JSON_EXTRACT(field, path))
   * path指向的对象必须确定为基本数据类型或字符串，不能是数组或对象，否则无法正确查询
   *
   * @throws JsonProcessingException
   */
  public void whereJsonContainsAny(String field, Collection value, String path) throws JsonProcessingException {
    final String valueJson = writer.writeValueAsString(value);
    String paramPath = getParameterizedName("path", path);
    String paramValue = getParameterizedName("value", valueJson);
    String sql = "JSON_CONTAINS(:" + paramValue + ", JSON_EXTRACT(`" + field + "`, :" + paramPath + "))";
    this.where.add(sql);
  }

  /**
   * Where path1 或 path2 或 ... pathN 包含values中的所有元素
   */
  public void whereAnyJsonPathContainsAny(String field, Collection values, Collection<String> paths) throws JsonProcessingException {
    // 不能直接使用 for path in paths:
    //     json_contains(values, json_extract(field, path))
    // 因为 json_extract不一定全是单个的值，会解压出数组
    MySQLBuilder sqlBuilder = this.or();
    sqlBuilder.counter = this.counter;
    Iterator it = values.iterator();
    while (it.hasNext()) {
      Object value = it.next();
      counter++;
      for (String jsonPath : paths) {
        sqlBuilder.whereJsonContains(field, value, jsonPath);
      }
    }
    sqlBuilder.build();
  }

  public void where(String field, String operator, Object value) {
    String parameterized = getParameterizedName("value", value);
    String sql = field + operator + ":" + parameterized;
    this.where.add(sql);
  }

  public void where(String sql, Object... sqlParameters) {
    this.where.add(sql);
    for (Object sqlParameter : sqlParameters) {
      getParameterizedName("param", sqlParameter);
    }
  }

  public List<String> getWhere() {
    return where;
  }

  public void setParameters(Query query) {
    this.valueNameMap.forEach((value, name) -> {
      query.setParameter(name, value);
    });
  }

}
